/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.cef.spi.entity.info.propertyinfo;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.inspur.edp.bef.api.lcp.LcpFactoryManagerUtils;
import com.inspur.edp.cef.api.manager.serialize.CefSerializeContext;
import com.inspur.edp.cef.entity.changeset.ValueObjModifyChangeDetail;
import com.inspur.edp.cef.entity.entity.AssoInfoBase;
import com.inspur.edp.cef.entity.entity.ICefData;
import com.inspur.edp.cef.entity.entity.IValuesContainer;
import com.inspur.edp.cef.spi.entity.AssociationEnableState;
import com.inspur.edp.cef.spi.entity.AssociationInfo;
import com.inspur.edp.cef.spi.entity.resourceInfo.builinImpls.CefEntityResInfoImpl;
import com.inspur.edp.cef.spi.entity.resourceInfo.builinImpls.CefModelResInfoImpl;
import com.inspur.edp.cef.spi.entity.resourceInfo.builinImpls.CefValueObjModelResInfo;
import com.inspur.edp.cef.spi.entity.resourceInfo.builinImpls.CefValueObjResInfo;
import com.inspur.edp.cef.spi.jsonser.base.StringUtils;
import com.inspur.edp.cef.spi.jsonser.util.SerializerUtil;
import com.inspur.edp.udt.api.Manager.IUdtManager;
import com.inspur.edp.udt.api.Manager.serialize.IAssoInfoSerializeItem;
import com.inspur.edp.udt.api.Manager.serialize.NestedSerializeContext;
import com.inspur.edp.udt.api.UdtManagerUtils;
import com.inspur.edp.udt.entity.ISimpleUdtData;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class SimpleAssoUdtPropertyInfo extends SimpleUdtPropertyInfo {

  private AssocationPropertyInfo assoInfo;
  private HashMap<String,String> mapping=new HashMap<>();
  private Class enrichedAssType;

  public SimpleAssoUdtPropertyInfo(String udtConfigId, boolean enableStdTimeFormat) {
    super(udtConfigId, enableStdTimeFormat);
  }

  public SimpleAssoUdtPropertyInfo(String udtConfigId,boolean enableStdTimeFormat,
      AssociationEnableState enableState,String privateTargetColumn)
  {
    this(udtConfigId,enableStdTimeFormat);
    AssociationInfo assInfo =initAssoInfo(enableState,privateTargetColumn);

    assoInfo=new AssocationPropertyInfo(assInfo);
  }

  private AssociationInfo initAssoInfo(AssociationEnableState enableState,
      String privateTargetColumn) {
    IUdtManager udtManager = (IUdtManager) UdtManagerUtils.getUdtFactory()
        .createUdtManager(this.udtConfigId);

      CefValueObjResInfo valueObjResInfo =getRefUdtResInfo();
      DataTypePropertyInfo dataTypePropertyInfo = valueObjResInfo.getEntityTypeInfo()
          .getPropertyInfoWithoutCache(valueObjResInfo.getEntityCode());
      dataTypePropertyInfo.getObjectInfo();
      AssocationPropertyInfo assocationPropertyInfo = (AssocationPropertyInfo) dataTypePropertyInfo
          .getObjectInfo();
      AssociationInfo ass = assocationPropertyInfo.getAssociationInfo();
      AssociationInfo associationInfo = new AssociationInfo(ass.getConfig(), ass.getNodeCode(),
          ass.getMainCode(), enableState, ass.getPrivateSourceColumn(), privateTargetColumn);
      return associationInfo;

  }

  private CefValueObjResInfo getRefUdtResInfo()
  {
    IUdtManager udtManager = (IUdtManager) UdtManagerUtils.getUdtFactory()
        .createUdtManager(this.udtConfigId);
    CefValueObjModelResInfo valueObjModelResInfo = null;
    try {
      valueObjModelResInfo = (CefValueObjModelResInfo) udtManager
          .getClass().getMethod("getUdtModelInfo").invoke(udtManager);
      CefValueObjResInfo valueObjResInfo = (CefValueObjResInfo) valueObjModelResInfo
          .getCustomResource(valueObjModelResInfo.getRootNodeCode());
      return  valueObjResInfo;
    } catch (IllegalAccessException e) {
      throw new RuntimeException();
    } catch (InvocationTargetException e) {
      throw new RuntimeException();
    } catch (NoSuchMethodException e) {
      throw new RuntimeException();
    }

  }

  public AssocationPropertyInfo getAssoInfo() {
    return assoInfo;
  }

  public void setAssoInfo(AssocationPropertyInfo assoInfo) {
    this.assoInfo = assoInfo;
  }

  @Override
  public void setValue(Object data, String propertyName, Object value, CefSerializeContext serContext) {
    if (data instanceof ValueObjModifyChangeDetail) {
      String[] names = propertyName.split("_", 2);
      String realName = names[1];

      ValueObjModifyChangeDetail change = (ValueObjModifyChangeDetail) data;

      //todo 临时return验证后续功能，等小袁回来修复后，再调整过来，或者直接抛异常
      if(change == null || change.getPropertyChanges().size() < 1)
        return ;

      JsonNode node = (JsonNode) value;

      AssoInfoBase asso = (AssoInfoBase)change.getPropertyChanges().values().stream().findFirst().get();

      AssociationInfo associationInfo = assoInfo.getAssociationInfo();
      String keyName = associationInfo.getPrivateTargetColumn();
      if (propertyName.equals(keyName) || propertyName.equals(StringUtils.toCamelCase(keyName))) {
        asso.setValue(keyName, node.textValue());
        return;
      }
      if (!associationInfo.getRefPropInfos().containsKey(propertyName))
        return;
      DataTypePropertyInfo refPropertyInfo = associationInfo.getRefPropInfos().get(propertyName);
      Object refValue = refPropertyInfo.read(node, serContext);
      asso.setValue(realName, refValue);

//      change = (ValueObjModifyChangeDetail)SerializerUtil.readNestedChange(udtConfigId, nodes, change, context);

//      return;
    }
  }

  @Override
  public Object read(JsonParser reader, String propertyName, DeserializationContext serializer,
      CefSerializeContext serContext) {
    Object superValue = super.read(reader, propertyName, serializer, serContext);
    if(!hasEnrichedProps() || superValue == null || !(superValue instanceof ISimpleUdtData)){
      return superValue;
    }
    Object info = ((ISimpleUdtData)superValue).getValue();
    return info;
  }

  @Override
  public void write(JsonGenerator writer, String propertyName, Object value,
      SerializerProvider serializer, CefSerializeContext serContext) {
    if (!hasEnrichedProps()) {
      super.write(writer, propertyName, value, serializer, serContext);
      return;
    }
    SerializerUtil
        .writeNestedValue(writer, serializer, getUdtConfigId(), (ICefData) value, propertyName,
            getNestedSerContext());
  }

  @Override
  public Object readChange(JsonParser reader, String propertyName,
      DeserializationContext serializer, CefSerializeContext serContext) {
    if (!hasEnrichedProps()) {
      return super.readChange(reader, propertyName, serializer, serContext);
    }

    JsonNode node = null;
    try {
      node = (JsonNode) reader.readValueAsTree();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    ValueObjModifyChangeDetail change = (ValueObjModifyChangeDetail) SerializerUtil
        .readNestedChange(getUdtConfigId(), node, serializer, getNestedSerContext());
    String key = (String) change.getPropertyChanges().keySet().stream().findFirst().get();
    AssoInfoBase value = (AssoInfoBase) change.getPropertyChanges().get(key);
    try {
      value = (AssoInfoBase) getAssoInfo().getAssoType().getConstructor(value.getClass())
              .newInstance(value);
    } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
      throw new RuntimeException("类型" + getAssoInfo().getAssoType().getName() + "实例化失败", e);
    } catch (NoSuchMethodException e) {
      e.printStackTrace();
    }

    node = node.get("ChangeInfo");
    if (node != null && node.fields().hasNext()) {
      node = node.fields().next().getValue();
      Iterator<Map.Entry<String, JsonNode>> iterator = node.fields();
      while (iterator.hasNext()) {
        Map.Entry<String, JsonNode> next = iterator.next();
        DataTypePropertyInfo refPropInfo = getAssoInfo().getAssociationInfo()
            .getEnrichedRefPropInfos().get(next.getKey());
        if (refPropInfo == null) {
          continue;
        }
        JsonParser parser = next.getValue().traverse(new ObjectMapper());
        try {
          parser.nextToken();
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
        value.setValue(refPropInfo.getPropertyName(),
            refPropInfo.read(parser, serializer, serContext));
      }
    }

    change.getPropertyChanges().put(key, value);
    return change;
  }

  public final boolean hasEnrichedProps() {
    return !getAssoInfo().getAssociationInfo().getEnrichedRefPropInfos().isEmpty();
  }

  @Override
  protected NestedSerializeContext getNestedSerContext() {
    boolean enableStdTimeFormat = super.getNestedSerContext().getEnableStdTimeFormat();
    return new NestedSerializeContext() {{
      setEnableStdTimeFormat(enableStdTimeFormat);
      setAssosicationEnrichedSerializer(new EnrichedAssoInfoSerItem(enableStdTimeFormat,
          getAssoInfo().getAssociationInfo().getEnrichedRefPropInfos()));
    }};
  }

  public HashMap<String, String> getMapping() {
    return mapping;
  }

  public final Class getEnrichedAssType() {
    return enrichedAssType;
  }

  public final void setEnrichedAssType(Class enrichedAssType) {
    this.enrichedAssType = enrichedAssType;
  }

  private class EnrichedAssoInfoSerItem implements IAssoInfoSerializeItem {

    private final boolean std;
    private final Map<String, DataTypePropertyInfo> enrichedProps;

    public EnrichedAssoInfoSerItem(boolean std, Map<String, DataTypePropertyInfo> enrichedProps) {
      this.std = std;
      this.enrichedProps = enrichedProps;
    }

    @Override
    public void writeAssociationInfo(JsonGenerator jsonGenerator, Object o,
        SerializerProvider serializerProvider) {
      CefSerializeContext ctx = new CefSerializeContext() {{
        setEnableStdTimeFormat(std);
      }};
      for (Map.Entry<String, DataTypePropertyInfo> pair : enrichedProps.entrySet()) {
        pair.getValue().write(jsonGenerator, ((IValuesContainer) o).getValue(pair.getKey()),
            serializerProvider, ctx);
      }
    }

    @Override
    public boolean readAssociationInfo(DeserializationContext deserializationContext, Object o,
        String s, Object o1) {
      return false;
    }

  }

//

  public final void addRefProperty(String propertyName,String refUdtPropertyName,String refPropertyName)
  {
    this.mapping.put(refUdtPropertyName,propertyName);
    getAssoInfo().getAssociationInfo().addRefProperty(propertyName,"",refPropertyName);
  }

  protected DataTypePropertyInfo interalGetRefProperty(String propertyName, String propertyDisplayKey,
      String refPropertyName) {
    CefValueObjResInfo cefValueObjResInfo=getRefUdtResInfo();

    AssocationPropertyInfo assocationPropertyInfo = (AssocationPropertyInfo) cefValueObjResInfo.getEntityTypeInfo()
        .getPropertyInfoWithoutCache(cefValueObjResInfo.getEntityCode()).getObjectInfo();
    DataTypePropertyInfo dataTypePropertyInfo=assocationPropertyInfo.getAssociationInfo().getRefPropInfos().get(refPropertyName);
    return new  RefDataTypePropertyInfo(propertyName,
        propertyDisplayKey, dataTypePropertyInfo.getRequired(),
        dataTypePropertyInfo.getEnableRtrim(),
        dataTypePropertyInfo.getLength(), dataTypePropertyInfo.getPrecision(),
        dataTypePropertyInfo.getFieldType(),
        dataTypePropertyInfo.getObjectType(),
        (BasePropertyInfo) dataTypePropertyInfo.getObjectInfo(),
        dataTypePropertyInfo.getDefaultValue(), dataTypePropertyInfo.isMultiLaguange(),
        dataTypePropertyInfo.getIsBigNumber(),dataTypePropertyInfo.getPropertyName());
  }

  public final void addEnrichedRefProperty(String propertyName,String refUdtPropertyName,String refPropertyName)
  {
    this.mapping.put(refUdtPropertyName,propertyName);
    getAssoInfo().getAssociationInfo().addEnrichedRefProperty(propertyName,"",refPropertyName);

  }
}
